package com.sp.business.normalization.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.generated.xml.firemon.NetworkObject;
import com.sp.generated.xml.firemon.NetworkObjectCollection;
import com.sp.generated.xml.firemon.StringProperty;

public class NetworkObjectKeyGenerator
{
  private static final Logger logger = LoggerFactory.getLogger(NetworkObjectKeyGenerator.class);

  public static void generateNetworkObjectKeys(NetworkObjectCollection previous, NetworkObjectCollection current)
  {
    // key statistics
    int matchedKeyCount = 0;
    int generatedKeyCount = 0;

    long start = System.currentTimeMillis();

    List<NetworkObject> nonMatchedNetworkObjects = new ArrayList<NetworkObject>(current.getNetworkObjects());
    List<NetworkObject> nonMatchedPreviousNetworkObjects;

		/*
		 * Cisco v1 normalization was in DC, it used hash to generate UUID some
		 * network objects had identical UUID which cause UI display and
		 * comparison problem
		 */
		// prevent copy duplicate key
	Set<String> copiedKeySet = new HashSet<String>();

    if (previous == null)
      nonMatchedPreviousNetworkObjects = new ArrayList<NetworkObject>();
    else
      nonMatchedPreviousNetworkObjects = new ArrayList<NetworkObject>(previous.getNetworkObjects());

    // find exact matches first
    for (NetworkObject networkObject : current.getNetworkObjects())
    {
      NetworkObject previousNetworkObject = findNetworkObjectExactMatch(nonMatchedPreviousNetworkObjects, networkObject);

      if (previousNetworkObject == null)
        continue;

      copyUID(previousNetworkObject, networkObject, copiedKeySet);

      // remove the previous networkObject, so it doesn't get matched again
      nonMatchedPreviousNetworkObjects.remove(previousNetworkObject);

      // remove the networkObject, so we don't try to match it again
      nonMatchedNetworkObjects.remove(networkObject);

      matchedKeyCount++;
    }

    // now try to match remaining, non-exact-matched users
    for (NetworkObject networkObject : nonMatchedNetworkObjects)
    {
      NetworkObject previousNetworkObject = findNetworkObjectCloseMatch(nonMatchedPreviousNetworkObjects, networkObject);

      // copy or generate the guid
	  copyUID(previousNetworkObject, networkObject, copiedKeySet);

      if (previousNetworkObject == null)
      {
        /* no close match could be found for the object.
         * the object is either a newly-created object, 
         * or it has changed so significantly that 
         * it is effectively a new object.
        */
        generatedKeyCount++;
      }
      else
      {
        // we found a close match,
        // remove the previous securityRule, so it doesn't get matched again
        nonMatchedPreviousNetworkObjects.remove(previousNetworkObject);

        matchedKeyCount++;
      }
    }

    logger.info(String.format("Matched %s and generated %s network object keys in %s ms.", matchedKeyCount,
        generatedKeyCount, System.currentTimeMillis() - start));
  }

  private static String copyUID(NetworkObject previous,
			NetworkObject current, Set<String> copiedKeySet)
  {
    String guid = null;

    boolean foundDuplicate = false;
    if (previous != null)
    {
			// do not copy duplicated key
      guid = previous.getUID();
      if (copiedKeySet.contains(guid)) {
				foundDuplicate = true;
				logger.info("found duplicate key: " + guid);
			} else {
				copiedKeySet.add(guid);
			}
		}

    if (StringUtils.isBlank(guid) || !NormalizationUtil.isStringUUID(guid)
				|| foundDuplicate == true)
      guid = String.format("{%s}", UUID.randomUUID().toString().toUpperCase());
    
    current.setUID(guid);

    current.setXsid("NetworkObject" + guid.replaceAll("\\{", "").replaceAll("\\}", ""));

    return guid;
  }

  private static NetworkObject findNetworkObjectExactMatch(
      List<NetworkObject> previousNetworkObjects,
      NetworkObject networkObject)
  {
    for (NetworkObject previousNetworkObject : previousNetworkObjects)
    {
      if (isNetworkObjectExactMatch(previousNetworkObject, networkObject))
        return previousNetworkObject;
    }

    return null;
  }

  private static boolean isNetworkObjectExactMatch(NetworkObject previousNetworkObject, NetworkObject networkObject)
  {   
    // test name
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getName(), networkObject.getName()))
      return false;

    // test networkObject type
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getType(), networkObject.getType()))
      return false;

    //TODO: jcoon - test netscreen networkObject group members?

    // test ip address
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getIPAddress(), networkObject.getIPAddress()))
      return false;

    // test ip address end
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getIPAddressEnd(), networkObject.getIPAddressEnd()))
      return false;

    // test ip address start
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getIPAddressStart(), networkObject.getIPAddressStart()))
      return false;

    // test netmask
    if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getNetmask(), networkObject.getNetmask()))
      return false;
    
    //test zone if both are populated
    //older version does not have zone concept
    if (previousNetworkObject.getZone() != null)
    {
        if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getZone(), networkObject.getZone()))
        return false;
    }
    
    //test translated ip
    if(!KeyGeneratorBase.isExactMatch(previousNetworkObject.getTranslatedAddress(), networkObject.getTranslatedAddress()))
    		return false;

    return true;
  }

  private static NetworkObject findNetworkObjectCloseMatch(
      List<NetworkObject> previousNetworkObjects,
      NetworkObject networkObject)
  {
    int highestPercentMatching = 0; 
    NetworkObject bestMatch = null;

    for (NetworkObject previousNetworkObject : previousNetworkObjects)
    {
      // can't change the networkObject type, if the type changes, it's a different networkObject
      if (!KeyGeneratorBase.isExactMatch(previousNetworkObject.getType(), networkObject.getType()))
        continue;
      
      int percentMatching = getPercentMatch(previousNetworkObject, networkObject);

      if (percentMatching > highestPercentMatching)
      {
        highestPercentMatching = percentMatching;
        bestMatch = previousNetworkObject;
      }
    }

    // TODO: jcoon - need to make this networkObject matching threshold configurable
    float minimumPercentMatching = 85;

    if (highestPercentMatching < minimumPercentMatching)
      return null;

    return bestMatch;
  }

  /** 
   * Find the percentage of similarity between two NetworkObjects.
   * 
   * %similar = 100 - (D / V) 
   * 
   * where D = number of deltas and V = the total number of values and percentage of change of all values
   * is averaged (mean) 
   * 
   * @param previousNetworkObject
   * @param networkObject
   * @return int in the range [0, 100]
   */
  private static int getPercentMatch(NetworkObject previousNetworkObject, NetworkObject networkObject)
  {
    float totalPercentageChange = 0;

    // name
    totalPercentageChange = getPercentChange(previousNetworkObject.getName(), networkObject.getName());

    //TODO: jcoon - test netscreen networkObject group members?

    // test ip address
    totalPercentageChange += getPercentChange(previousNetworkObject.getIPAddress(), networkObject.getIPAddress());

    // test ip address end
    totalPercentageChange += getPercentChange(previousNetworkObject.getIPAddressEnd(), networkObject.getIPAddressEnd());

    // test ip address start
    totalPercentageChange += getPercentChange(previousNetworkObject.getIPAddressStart(),
        networkObject.getIPAddressStart());

    // test netmask
    totalPercentageChange += getPercentChange(previousNetworkObject.getNetmask(), networkObject.getNetmask());
    
    //test translated ip
    totalPercentageChange += getPercentChange(previousNetworkObject.getTranslatedAddress(), networkObject.getTranslatedAddress());
    
    return (int)(100 * (1 - (totalPercentageChange / 6))); //take the average percentage of change for each constituent value
  }

  /**
   * @param property1
   * @param property2
   * @return float in the range [0.0, 1.0]
   */
  private static float getPercentChange(final StringProperty property1, final StringProperty property2)
  {
    int prop1Length = NormalizationUtil.length(property1);
    int prop2Length = NormalizationUtil.length(property2);
    
    if(prop1Length == 0 ^ prop2Length == 0)
    {
      return 1.0f;
    }
    else if(prop1Length == 0)
    {
      return 0.0f;
    }
    
    final float percentChange = Math.min(1.0f, (float) KeyGeneratorBase.getEditDistance(property1, property2)
        / (float) NormalizationUtil.length(property1));
    return percentChange;
  }
}
